#include "stdlib.hpp"
#include <purewell/memory.hpp>
#include <purewell/file.hpp>

namespace purewell
{

class __pwmemblk
{
public:
	inline virtual PWMemory::MEM_TYPE getType(void) const
	{
		return PWMemory::MEM_NONE;
	}

	inline virtual void destroy(void) = 0;

	inline size_t getSize(void) const
	{
		return m_size;
	}

	inline void* getBlock(void)
	{
		return m_mem;
	}

	inline const void* getBlock(void) const
	{
		return m_mem;
	}

	inline virtual bool isValid(void) const
	{
		return NULL != m_mem;
	}

public:
	inline __pwmemblk() : m_size(0), m_mem(NULL) {}
	inline virtual ~__pwmemblk() {}

protected:
	size_t		m_size;
	void*		m_mem;
};

class __pwmemblk_normal : public __pwmemblk
{
public:
	inline PWMemory::MEM_TYPE getType(void) const
	{
		return PWMemory::MEM_NORMAL;
	}

	inline void destroy(void)
	{
		if ( NULL != m_mem )
		{
			free(m_mem);
			m_mem = NULL;
			m_size = 0;
		}
	}

	__pwmemblk_normal(size_t size)
	{
		void* ptr(malloc(size));
		if ( NULL == ptr )
		{
			return;
		}

		m_mem = ptr;
		m_size = size;
	}
};

class __pwmemblk_file : public __pwmemblk
{
public:
	inline PWMemory::MEM_TYPE getType(void) const
	{
		return PWMemory::MEM_FILE;
	}

	inline void destroy(void)
	{
		if ( NULL != m_mem )
		{
			munmap(m_mem, m_size);
			m_mem = NULL;
			m_size = 0;
		}
	}

	__pwmemblk_file(void* start, int fd, off_t offset,
		size_t size, int prot, int flag)
		: m_fd(fd), m_offset(offset), m_prot(prot), m_flag(flag)
	{
		void* ptr(mmap(start, size, prot, flag, fd, offset));
		if ( MAP_FAILED == ptr )
		{
			return;
		}

		m_mem = ptr;
		m_size = size;
	}

protected:
	int		m_fd;
	off_t	m_offset;
	int		m_prot;
	int		m_flag;
};

PWMemory::PWMemory(size_t size) : m_pData (NULL)
{
	__pwmemblk_normal* blk(new __pwmemblk_normal(size));
	if ( NULL == blk )
	{
		return;
	}

	if ( false == blk->isValid() )
	{
		delete blk;
		return;
	}

	m_pData = blk;
}

PWMemory::PWMemory(void* start, int fd, off_t offset,
	size_t size, int prot, int flag)
	: m_pData(NULL)
{
	__pwmemblk_file* blk(new __pwmemblk_file(start, fd, offset, size,
		prot, flag));
	if ( NULL == blk )
	{
		return;
	}

	if ( false == blk->isValid() )
	{
		delete blk;
		return;
	}

	m_pData = blk;
}

PWMemory::~PWMemory()
{
	if ( NULL != m_pData )
	{
		__pwmemblk* blk((__pwmemblk*)m_pData);
		delete blk;
		m_pData = NULL;
	}
}

PWMemory*
PWMemory::create(size_t size)
{
	PWMemory* mem(new PWMemory(size));
	if ( NULL == mem->m_pData )
	{
		delete mem;
		return NULL;
	}

	return mem;
}

PWMemory*
PWMemory::create(size_t size, PWFile& file, off_t offset,
	int omode, int flag, void* startptr)
{
	PWMemory* mem(new PWMemory(startptr, file.getHandle(), offset,
		size, omode, flag));
	if ( NULL == mem->m_pData )
	{
		delete mem;
		return NULL;
	}

	return mem;
}

PWMemory*
PWMemory::create(size_t size, int fd, off_t offset,
	int omode, int flag, void* startptr)
{
	PWMemory* mem(new PWMemory(startptr, fd, offset,
		size, omode, flag));
	if ( NULL == mem->m_pData )
	{
		delete mem;
		return NULL;
	}

	return mem;
}

void
PWMemory::release(PWMemory* obj)
{
	delete obj;
}

PWMemory::MEM_TYPE
PWMemory::getType(void) const
{
	if ( NULL == m_pData )
	{
		return MEM_NONE;
	}

	__pwmemblk* mem((__pwmemblk*)m_pData);
	return mem->getType();
}

size_t
PWMemory::getSize(void) const
{
	if ( NULL == m_pData )
	{
		return 0;
	}

	__pwmemblk* mem((__pwmemblk*)m_pData);
	return mem->getSize();
}

const void*
PWMemory::operator () (void) const
{
	if ( NULL == m_pData )
	{
		return NULL;
	}

	__pwmemblk* mem((__pwmemblk*)m_pData);
	return mem->getBlock();
}

void*
PWMemory::operator () (void)
{
	if ( NULL == m_pData )
	{
		return NULL;
	}

	__pwmemblk* mem((__pwmemblk*)m_pData);
	return mem->getBlock();
}

void*
PWMemory::getMemory(void)
{
	if ( NULL == m_pData )
	{
		return NULL;
	}

	__pwmemblk* mem((__pwmemblk*)m_pData);
	return mem->getBlock();
}

const void*
PWMemory::getMemory(void) const
{
	if ( NULL == m_pData )
	{
		return NULL;
	}

	__pwmemblk* mem((__pwmemblk*)m_pData);
	return mem->getBlock();
}

};//namespace purewell

